/*
	GadgetJumpGate.cpp
	(c)2004 Palestar Inc, Richard Lyle
*/


#include "Debug/Assert.h"
#include "Math/Helpers.h"
#include "GadgetJumpGate.h"
#include "NounJumpGate.h"
#include "NounStar.h"
#include "GameContext.h"
#include "Resource.h"

//----------------------------------------------------------------------------

Constant GADGET_JG_ORIGIN_OFFSET( "GADGET_JG_ORIGIN_OFFSET", 50.0f );

//----------------------------------------------------------------------------

IMPLEMENT_ABSTRACT_FACTORY( GadgetJumpGate, NounGadget ); 
REGISTER_FACTORY_KEY( GadgetJumpGate, 4626281911507602914 );

BEGIN_ABSTRACT_PROPERTY_LIST( GadgetJumpGate, NounGadget );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_nEnergy );
END_PROPERTY_LIST();

GadgetJumpGate::GadgetJumpGate() : m_nEnergy( 0 ), m_nEnergyTick( 0 )
{}

//----------------------------------------------------------------------------

void GadgetJumpGate::setup()
{
	NounGadget::setup();
	m_nEnergy = energyNeeded();
	m_nEnergyTick = tick();
}

void GadgetJumpGate::initialize()
{
	NounGadget::initialize();
	m_nEnergyTick = tick();
}

//----------------------------------------------------------------------------

NounGadget::Type GadgetJumpGate::type() const
{
	return JUMP_GATE;
}

dword GadgetJumpGate::hotkey() const
{
	return 'J';
}

//----------------------------------------------------------------------------

int GadgetJumpGate::usableWhen() const
{
	return (100 - ((m_nEnergy * 100) / energyNeeded()));
}

bool GadgetJumpGate::usable( Noun * pTarget, bool shift ) const
{
	if ( destroyed() )
		return false;		// destroyed
	if ( m_nEnergy < energyNeeded() )
		return false;		// still charging
	if ( !shift && !validateTarget( pTarget ) )
		return false;		// not a valid target
	if ( gateContext() == NULL )
		return false;		// no gate context
	if ( parentBody() == NULL )
		return false;		// not attached to a NounBody object

	if( !shift )
	{
		// check the range to the destination
		float fDistance = (worldPosition() - pTarget->worldPosition()).magnitude();
		if ( fDistance < minRange() || fDistance > range() )
			return false;		// out of range

	}

	return true;
}

void GadgetJumpGate::use( dword when, Noun * pTarget, bool shift)
{
	if ( server() )
	{
		Noun * pParent = parentBody();
		ASSERT( pParent );

		// create the jumpgates
		NounJumpGate * pGate1 = new NounJumpGate;
		pGate1->setNounContext( gateContext() );
		pGate1->calculateRadius();
		pGate1->setName( pGate1->nounContext()->name() );
		pGate1->setDuration( true, duration() );
		pGate1->setActive( false );						// set the gate to inactive, so it will create a use effect when it gets attached and activated
		pGate1->setTick( when );

		NounJumpGate * pGate2 = new NounJumpGate;
		pGate2->setNounContext( gateContext() );
		pGate2->calculateRadius();
		pGate2->setName( pGate2->nounContext()->name() );
		pGate2->setDuration( true, duration() );
		pGate2->setActive( false );	
		pGate2->setTick( when );

		// link the gates
		pGate1->setDestination( pGate2 );
		pGate2->setDestination( pGate1 );
		
		// The variance factor increases the variance of the wormhole position up to factor 4, based on the creating ships speed.
		int nVarianceFactor = 1;
		NounShip * pParentShip = WidgetCast< NounShip >( pParent );
		if( pParentShip )
		{
			if( pParentShip->velocity() > 20.0 ) 
				nVarianceFactor = 4;
			else if( pParentShip->velocity() > 1.0 ) 
				nVarianceFactor = 1 + 0.15 * pParentShip->velocity();
		}

		Vector3 vTargetPosition;
		if( shift )
		{
			// Find a target for the emergency gate. Target shouldn't be too close
			// Preference: Friendly planet with shipyard, without shipyard, random place in space
			Array< Noun * > arPlanets;
			Array< Noun * > arPlanetsSY;

			Array< GameContext::NounCollision > contacts;
			context()->proximityCheck( pParent->worldPosition(), 100000.0f, contacts, CLASS_KEY(NounPlanet) );

			for(int i=0;i<contacts.size();i++)
			{
				Noun * pContact = contacts[ i ].pNoun;
				if ( WidgetCast<NounPlanet>( pContact ) && pContact->isFriend( pParent ) && contacts[ i ].fDistance > minRange() )
				{
					arPlanets.push( pContact );
					
					if( ( ((NounPlanet*)pContact)->flags() & NounPlanet::FLAG_HAS_SHIPYARD ) != 0 )
						arPlanetsSY.push( pContact );
				}
			}

			// find a suitable target
			if ( arPlanetsSY.size() > 0 )
				vTargetPosition = ((NounPlanet*)arPlanetsSY[ rand() % arPlanetsSY.size() ])->worldPosition();
			else if ( arPlanets.size() > 0 )
				vTargetPosition = ((NounPlanet*)arPlanets[ rand() % arPlanets.size() ])->worldPosition();
			else
			{	
				int nTriesLeft = 10;
				while( nTriesLeft-- > 0 )
				{
					vTargetPosition = Vector3( minRange() + rand() % (int)( range() / 2 ), 0, minRange() + rand() % (int)( range() / 2 ) );
					int nDistance = ( pParent->worldPosition() - vTargetPosition ).magnitude();
					if( nDistance > minRange() && nDistance < range() )
						nTriesLeft = 0;		// found a good target
				}
				// Doesn't matter if the tries run out, there is still a targetPosition then, it may not neccesarily be the best though

			}
		}
		else
			vTargetPosition = pTarget->worldPosition();

		// try to find a suitable place for the destination gate in 5 random places near the target before giving up
		int nTriesLeft = 5;
		bool bSucceeded = false;

		do {	
			Vector3 vDirection = RandomVector( -1, 1 );
			vDirection.y = 0.0f;
			vDirection.normalize();

			pGate1->setPosition( vTargetPosition + (vDirection * RandomFloat( 0, variation() * nVarianceFactor )) );
			pGate1->setFrame( Matrix33( vDirection ) );
			
			Array< GameContext::NounCollision > collisions;
			if ( context()->proximityCheck( pGate1->position(), pGate1->radius() * 1.5, collisions ) )
			{
				if( collisions.size() <= 5 )	
				{	
					bool bDictoryOnly = true;
					for( int i = 0 ; i < collisions.size() ; i++ )
					{
						NounField * pNF = WidgetCast< NounField>( collisions[ i ].pNoun );
						if( !pNF || !pNF->isFriend( pParent ) )
						{
							bDictoryOnly = false;
							break;						// No interdictor field of the own team. This will fail
						}
					}

					bSucceeded = bDictoryOnly;
				}
			}
			else
				bSucceeded = true;						// not a single noun overlapping the wormhole at this position.

		} while( !bSucceeded && --nTriesLeft >= 0 );

		if ( bSucceeded )
		{
			// A suitable position for the destination WH was found.
			// However the fixed position for the WH in front of the creating ship might be blocked
			// So this needs to be checked too

			// now set the position of the origin gate
			if ( pParent->zone() != NULL )
			{
				float fOriginRadius = pParent->radius() + pGate2->radius() + GADGET_JG_ORIGIN_OFFSET;
				pGate2->setPosition( pParent->position() + (pParent->frame().k * fOriginRadius ) );
				pGate2->setFrame( pParent->frame() );
				
				// check if its too close to a jumpgate, planet or star
				Array< GameContext::NounCollision > collisions;
				if ( context()->proximityCheck( pGate2->position(), Min( pGate2->radius() * 1.5, 110.0 ), collisions ) )
				{	
					for( int i = 0 ; i < collisions.size() ; i++ )
					{
						Noun * pNoun = collisions[i].pNoun;
						if( WidgetCast< NounPlanet>( pNoun ) != NULL
							|| WidgetCast< NounJumpGate>( pNoun ) != NULL 
							|| WidgetCast< NounStar>( pNoun ) != NULL )
						{
							bSucceeded = false;
							break;
						}
					}
				}
			}
			else
				bSucceeded = false;
		}
		else
			pParent->privateChat( "Helm: Failed to create destination wormhole!" );

		
		if( bSucceeded )
		{
			// everythings still fine. Attach both WHs.
			ASSERT( pParent->zone() );

			pGate2->setSyncronized( false );
			pParent->zone()->attachNoun( pGate2 );

			pGate1->setSyncronized( false );
			context()->attachNoun( pGate1 );
		}
		else
			pParent->privateChat( "Helm: Failed to create source wormhole!" );

	}

	createUseEffect( false );
	m_nEnergy = 0;
	m_nEnergyTick = when;
}

int GadgetJumpGate::useEnergy( dword nTick, int energy )
{
	if ( m_nEnergy < energyNeeded() )
	{
		int nElapsed = nTick - m_nEnergyTick;
		m_nEnergyTick = nTick;

		int use =  damageRatioInv() * chargeRate();
		int charge = Min( Min( energyNeeded() - m_nEnergy, use ), energy ) * nElapsed;

		energy -= charge;
		m_nEnergy += charge;
	}

	return energy;
}

//----------------------------------------------------------------------------

NounContext * GadgetJumpGate::gateContext() const
{
	return WidgetCast<NounContext>( nounContext()->resource( "GATE" ) );
}

//----------------------------------------------------------------------------
//EOF
